
// 这种方式不能形成模块且不能在 原型上添加方法
// export function myCall() {
//   console.log('this is myCall function');
// }

// 重写方法(ES3方式)

const utilsModule = ((Function) => {

  // test.call({
  //   a: 1,
  //   b: 2
  // }, 'c', 'd')

  // context 执行上下文 调用时的第一个参数
  Function.prototype.myCall = function (ctx) {
    // ctx => { a: 1, b: 2 } => object
    // 当前的this 指向调用它的函数 ，要把这个函数里面的this指向 改变为 ctx
    // this => 调用它的函数(test) => this => ctx
    // ctx = ctx || window;
    ctx = ctx ? Object(ctx) : window;
    // originFn & test(调用的函数) => 都指向同一个内存地址
    ctx.originFn = this; // 引用

    var _args = [];
    
    // 拿到myCall的第二个参数 开始到结束的所有参数 作为test(调用的函数)的实参列表
    for (var i = 1; i < arguments.length; i ++) {
      // 不能直接装 arguments 的参数, 因为最终是要执行 ctx.originFn
      // args + 字符串 -> args 展开平铺到fn的实参中
      _args.push('arguments[' + i + ']');
    }

    // ctx.originFn(['arguments[1]', 'arguments[2]']) // 如果其中一个用字符串拼接
    // ctx.originFn([arguments[1], arguments[2]]) // 如果都不用字符串拼接
    // 用eval 执行函数 使用 + '' 的隐式转换
    // ctx.originFn(arguments[1],arguments[2])
    var res = eval('ctx.originFn(' + _args + ')');
    // 因为这是自己添加的, 执行完成后需要删除 
    delete ctx.originFn;

    return res;
  }

  Function.prototype.myApply = function (ctx, args) {
    ctx = ctx ? Object(ctx) : window;
    ctx.originFn = this;
    var _res = null;
    var _args = [];

    // 如果是原始值就报类型错误
    if (typeof args !== 'object' && typeof args !== 'function')
      throw new TypeError('CreateListFromArrayLike called on non-object');

    // 如果第二个参数没有传或不是数组就直接执行
    if (!args || typeOf(args) !== 'Array') {
      _res = ctx.originFn();
    } else {
      for (var i = 0; i < args.length; i ++) {
        _args.push('args[' + i + ']');
      }

      _res = eval('ctx.originFn(' + _args + ')');
    }
    
    delete ctx.originFn;

    return _res;
  }

  Function.prototype.myBind = function (ctx) {
        // 保存调用的函数(this)
    var originFn = this,
        // bind传递的参数列表
        args = [].slice.call(arguments, 1),
        // 原型传递中介函数
        _tempFn = function () {};

    var newFn = function() {
      // 新返回的函数传递的参数列表
      var newArgs = [].slice.call(arguments);
      // 可能有返回值 要return 且要判断 这个this的指向， 是否是函数构造出来的
      // 如果是 new， this -> 构造出来的那个函数相当于 实例化  this -> 当前这个 new出来的实例
      return originFn.apply(this instanceof newFn ? this: ctx, args.concat(newArgs));
    }
    // 新返回的函数与之前调用函数需要关联(直接赋值prototype不好，使用圣杯模式)
    // newFn.prototype = this.prototype;
    _tempFn.prototype = this.prototype;
    newFn.prototype = new _tempFn();

    return newFn;
  }

  // 封装类型检查方法 ES3
  function typeOf(value) {
    if (value === null) return 'null';

    // 使用 toString 类型检查
    // var types = {
    //   '[object Object]': 'Object',
    //   '[object Array]': 'Array',
    //   '[object Number]': 'Number',
    //   '[object String]': 'String',
    //   '[object Boolean]': 'Boolean'
    // };
    // if (typeof value === 'object') {
    //   var res = ({}).toString.call(value); // [object Object]
    //   var type = types[res]; // Object
    // }

    return typeof value === 'object' ? {
      '[object Object]': 'Object',
      '[object Array]': 'Array',
      '[object Number]': 'Number',
      '[object String]': 'String',
      '[object Boolean]': 'Boolean'
    }[({}).toString.call(value)] : typeof value;
  }

  return {
    typeOf
  }

})(Function);

export default utilsModule;